#ifndef __M_ROOM_H__
#define __M_ROOM_H__

#include "db.hpp"
#include "online_user.hpp"

#define BOARD_ROW 15 // 棋盘行数
#define BOARD_COL 15 // 棋盘列数

enum room_status
{
    GAMING, // 游戏中
    VACANT  // 空闲
};
enum chess_color
{
    CHESS_WHITE = 1, // 白棋
    CHESS_BLACK      // 黑棋
};

/* 房间信息类 */
class room
{
public:
    /* 初始化
        room_id由房间管理分配，
        utb和olu由外界传入
    */
    room(uint64_t room_id, user_table *utb, online_user *olu)
        : _room_id(room_id), _utb(utb), _olu(olu), _room_state(GAMING), _player_count(0), _board(BOARD_ROW, std::vector<int>(BOARD_COL, 0))
    {
        DBG_LOG("%lu 房间创建成功！等待玩家进入...", _room_id);
    }
    ~room()
    {
        DBG_LOG("玩家均已离开房间，%lu 房间销毁成功！", _room_id);
    }
    /* 获取房间id */
    uint64_t id() const
    {
        return _room_id;
    }
    /* 获取房间状态——游戏中or空闲 */
    room_status state() const
    {
        return _room_state;
    }
    /* 获取房间中的玩家数量 */
    int player_count() const
    {
        return _player_count;
    }
    /* 添加执白棋玩家到房间中 */
    void add_white_player(const uint64_t uid)
    {
        _white_uid = uid;
        ++_player_count;
    }
    /* 添加执黑棋玩家到房间中 */
    void add_black_player(const uint64_t uid)
    {
        _black_uid = uid;
        ++_player_count;
    }
    /* 获取房间中的执白棋玩家id */
    uint64_t get_white_player() const
    {
        return _white_uid;
    }
    /* 获取房间中的执黑棋玩家id */
    uint64_t get_black_player() const
    {
        return _black_uid;
    }

    /* 处理下棋动作 */
    Json::Value handle_play_chess(Json::Value &req)
    {
        Json::Value resp = req;
        // 1. 当前请求的房间id是否与当前房间匹配
        // 在调用下棋的处理函数中校验

        // 2. 判断房间中两个玩家是否都在线，任意一个不在线，就是另一方胜利
        uint64_t cur_uid = req["uid"].asUInt64(); // 下棋用户的id
        int chess_row = req["row"].asInt();
        int chess_col = req["col"].asInt();
        if (_olu->is_in_room(cur_uid) == false)
        {
            // 当前下棋用户掉线，另一个用户胜利
            resp["result"] = true;
            resp["reason"] = "对方掉线，您获得胜利！";
            if (cur_uid == _white_uid)
                resp["winner"] = (Json::Value::UInt64)_black_uid;
            else
                resp["winner"] = (Json::Value::UInt64)_white_uid;

            // 更新用户信息 winner and loser
            // 在调用下棋函数的处理函数中更新

            return resp;
        }
        // 3. 获取走棋位置，判断当前走棋是否合法（即当前要下棋的位置是否已有棋子）
        if (_board[chess_row][chess_col] != 0)
        {
            resp["result"] = false;
            resp["reason"] = "当前位置已有棋子！";
            return resp;
        }

        chess_color cur_clr = cur_uid == _white_uid ? CHESS_WHITE : CHESS_BLACK;
        _board[chess_row][chess_col] = cur_clr;
        // 4. 判断玩家是否胜利（从当前走棋位置开始判断是否存在五星连珠）
        uint64_t win_uid = check_win(chess_row, chess_col, cur_clr);

        if (win_uid != 0)
        {
            resp["reason"] = "五星连珠！恭喜您获得胜利！";
            // 更新用户信息 winner and loser
            // 在调用下棋函数的处理函数中更新
        }

        resp["result"] = true;
        resp["winner"] = (Json::Value::UInt64)win_uid; // 0表示没有胜利

        return resp;
    }
    /* 处理聊天动作 */
    Json::Value handle_chat(Json::Value &req)
    {
        Json::Value resp(req);
        // 1. 检测房间是否一致
        // 在调用处理聊天动作函数的处理函数中校验

        // 2. 检测消息中是否有敏感词——此功能后续建议封装
        std::string msg = req["message"].asString();
        size_t pos = msg.find("垃圾"); // 举例
        if (pos != std::string::npos)
        {
            resp["result"] = false;
            resp["reason"] = "您的消息中包含敏感词，发送失败！";
            return resp;
        }
        // 广播消息
        resp["result"] = true;
        return resp;
    }
    /* 处理玩家退出动作——连接断开时调用 */
    void handle_exit(const uint64_t uid)
    {
        // 下棋中退出，判定对方胜利
        Json::Value resp;
        if (_room_state == GAMING)
        {
            resp["optype"] = "put_chess";
            resp["result"] = true;
            resp["reason"] = "对方掉线，您获得胜利！";
            resp["room_id"] = (Json::Value::UInt64)_room_id;
            resp["uid"] = (Json::Value::UInt64)uid;
            resp["row"] = -1;
            resp["col"] = -1;

            
            uint64_t win_uid = uid == _white_uid ? _black_uid : _white_uid;
            resp["winner"] = (Json::Value::UInt64)win_uid;
            uint64_t lose_uid = win_uid == _white_uid ? _black_uid : _white_uid;

            // 更新用户信息 winner and loser
            _utb->win(win_uid);
            _utb->lose(lose_uid);
            // 设置房间状态
            _room_state = VACANT;

            // 广播
            broadcast(resp);
        }
        // 下棋结束退出
        --_player_count;
    }

    /* 弹性处理请求——在函数内部，根据不同的请求调用不同的处理函数，得到响应 */
    void handle_request(Json::Value &req)
    {
        Json::Value resp;
        // 1. 校验房间号是否匹配
        uint64_t room_id = req["room_id"].asUInt64();
        if (room_id != _room_id)
        {
            resp["optype"] = req["optype"].asString();
            resp["result"] = false;
            resp["reason"] = "房间号不匹配！";

            std::string body;
            json_util::serialize(resp, &body);
            DBG_LOG("房间-广播信息：%s", body.c_str());

            broadcast(resp);
            return;
        }
        // 2. 根据请求类型调用相应的处理函数
        if (req["optype"].asString() == "put_chess") // 聊天处理
        {
            resp = handle_play_chess(req);
            uint64_t win_uid = resp["winner"].asUInt64();
            // 因为下棋中，不止一次更新用户信息，所以摘出来
            if (win_uid != 0)
            {
                // 更新用户信息 winner and loser
                _utb->win(win_uid);
                uint64_t lose_uid = win_uid == _white_uid ? _black_uid : _white_uid;
                _utb->lose(lose_uid);

                // 设置房间状态
                _room_state = VACANT;

                // 逐个发送响应
                single_broadcast(resp, win_uid);
                resp["reason"] = "对方获胜！请再接再厉！";
                single_broadcast(resp, lose_uid);
                return;
            }
        }
        else if (req["optype"].asString() == "chat")
        {
            resp = handle_chat(req);
        }
        else
        {
            resp["optype"] = req["optype"].asString();
            resp["result"] = false;
            resp["reason"] = "未知请求类型，错误！";
        }

        std::string body;
        json_util::serialize(resp, &body);
        DBG_LOG("房间-广播信息：%s", body.c_str());
        // 3. 广播响应
        broadcast(resp);
    }
    void single_broadcast(Json::Value &resp, const uint64_t uid)
    {
        // 1. 对要响应的Json::Value信息序列化为Json格式的字符串
        std::string body;
        json_util::serialize(resp, &body);
        // 2. 获取房间中指定用户的连接
        // 3. 发送响应
        wsserver_t::connection_ptr conn = _olu->get_conn_from_room(uid);
        if (conn.get() /* 获取指针对象 */ != nullptr)
        {
            // 发送响应
            conn->send(body);
        }
        else
        {
            DBG_LOG("房间-玩家UID[%lu]连接获取失败！", uid);
            return;
        }
    }
    /* 广播消息给房间中的所有玩家——白棋玩家和黑棋玩家 */
    void broadcast(Json::Value &resp)
    {
        // 1. 对要响应的Json::Value信息序列化为Json格式的字符串
        std::string body;
        json_util::serialize(resp, &body);
        // 2. 获取房间中所有用户的连接
        // 3. 逐个发送响应
        wsserver_t::connection_ptr white_conn = _olu->get_conn_from_room(_white_uid);
        if (white_conn.get() /* 获取指针对象 */ != nullptr)
        {
            // 发送响应
            white_conn->send(body);
        }
        else
        {
            DBG_LOG("房间-白棋玩家连接获取失败！");
        }
        wsserver_t::connection_ptr black_conn = _olu->get_conn_from_room(_black_uid);
        if (black_conn.get() /* 获取指针对象 */ != nullptr)
        {
            // 发送响应
            black_conn->send(body);
        }
        else
        {
            DBG_LOG("房间-黑棋玩家连接获取失败！");
        }

        DBG_LOG("房间-广播消息成功！");
    }

private:
    /* 判断五星连珠 */
    bool five(const chess_color color, const int row, const int col, const int row_offset, const int col_offset) const
    {
        int count = 1;
        int cur_row = row + row_offset;
        int cur_col = col + col_offset;
        // 正方向
        while (cur_row >= 0 && cur_row < BOARD_ROW && cur_col >= 0 && cur_col < BOARD_COL && _board[cur_row][cur_col] == color && count < 5)
        {
            ++count;
            cur_row += row_offset;
            cur_col += col_offset;
        }
        // 负方向
        cur_row = row - row_offset;
        cur_col = col - col_offset;
        while (cur_row >= 0 && cur_row < BOARD_ROW && cur_col >= 0 && cur_col < BOARD_COL && _board[cur_row][cur_col] == color && count < 5)
        {
            ++count;
            cur_row -= row_offset;
            cur_col -= col_offset;
        }

        return count == 5;
    }
    /* 判断当前走棋是否胜利 */
    uint64_t check_win(const int row, const int col, const chess_color color) const
    {
        /* 返回胜利玩家id，若没有胜利，返回0 */

        // 从下棋位置的四个方向上检测是否出现了五个及以上相同颜色的棋子
        if (five(color, row, col, 1, 0) ||
            five(color, row, col, 0, 1) ||
            five(color, row, col, 1, -1) ||
            five(color, row, col, 1, 1))
        {
            return color == CHESS_WHITE ? _white_uid : _black_uid;
        }

        return 0; // 没有胜利
    }

private:
    uint64_t _room_id;                    // 房间id - 由房间管理模块进行分配
    room_status _room_state;              // 房间状态
    int _player_count;                    // 房间中玩家个数 0 or 1
    uint64_t _white_uid;                  // 白棋玩家id
    uint64_t _black_uid;                  // 黑棋玩家id
    user_table *_utb;                     // 用户信息表 - 由房间管理模块进行分配
    online_user *_olu;                    // 在线用户管理句柄 - 由房间管理模块进行分配
    std::vector<std::vector<int>> _board; // 棋盘
};

using room_ptr = std::shared_ptr<room>;

class room_manager
{
public:
    /* 初始化房间id计数器 */
    room_manager(user_table *utb, online_user *olu)
        : _next_rid(1), _utb(utb), _olu(olu)
    {
        DBG_LOG("房间管理模块初始化完毕！");
    }
    ~room_manager()
    {
        DBG_LOG("房间管理模块销毁！");
    }

    /* 创建房间，返回房间的智能指针管理对象 */
    room_ptr create_room(const uint64_t uid1, const uint64_t uid2)
    {
        // 在游戏大厅的两个玩家匹配成功后创建房间

        // 1. 校验两个玩家是否都在游戏大厅
        if (!_olu->is_in_hall(uid1) || !_olu->is_in_hall(uid2))
        {
            DBG_LOG("有玩家不在大厅中，创建房间失败！");
            return room_ptr();
        }
        // 2. 创建房间，将用户信息添加到房间中
        std::unique_lock<std::mutex> lock(_mutex);

        room_ptr rmp(new room(_next_rid, _utb, _olu));
        rmp->add_white_player(uid1);
        rmp->add_black_player(uid2);
        // 3. 管理房间信息
        _rooms.insert(std::make_pair(_next_rid, rmp));

        // 4. 将用户加入到房间中——添加用户id和房间id的映射关系
        _users.insert(std::make_pair(uid1, _next_rid));
        _users.insert(std::make_pair(uid2, _next_rid));

        // 要分配的房间id自增
        ++_next_rid;

        // test
        // for(auto& e : _users)
        // {
        //     std::cout << "uid:" << e.first << "->" << "room_id:" << e.second << std::endl;
        // }

        DBG_LOG("用户已加入到房间[%lu]中", rmp->id());
        // 5. 返回房间信息
        return rmp;
    }
    /* 通过房间id获取房间信息 */
    room_ptr get_room_by_rid(const uint64_t rid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _rooms.find(rid);
        if (it == _rooms.end())
        {
            return room_ptr();
        }
        return it->second;
    }
    /* 通过用户id获取房间信息 */
    room_ptr get_room_by_uid(const uint64_t uid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        // 1. 通过用户id获取房间id
        auto uit = _users.find(uid);
        if (uit == _users.end())
        {
            return room_ptr();
        }
        // 这里不可重复加锁，会造成锁冲突
        // 2. 通过房间id获取房间信息
        uint64_t rid = uit->second;
        auto rit = _rooms.find(rid);
        if (rit == _rooms.end())
        {
            return room_ptr();
        }
        return rit->second;
    }
    /* 通过房间id销毁房间 */
    void remove_room(const uint64_t rid)
    {
        // 房间信息是通过shared_ptr在_rooms中管理的，要删除房间信息，只需要将相应的shared_ptr从_rooms中移除即可。
        // 这样，shared_ptr的计数器就为0，就会释放掉这个房间信息实体

        // 1. 通过房间id获取房间信息
        room_ptr rpm = get_room_by_rid(rid);
        if (rpm.get() == nullptr)
        {
            DBG_LOG("找不到该房间信息：%lu", rid);
            return;
        }
        // 2. 通过房间信息获取用户id
        uint64_t uid1 = rpm->get_white_player();
        uint64_t uid2 = rpm->get_black_player();
        // 3. 移除房间管理中的用户信息
        std::unique_lock<std::mutex> lock(_mutex);
        _users.erase(uid1);
        _users.erase(uid2);
        // 4. 移除房间信息
        _rooms.erase(rid);
    }
    /* 删除房间中的指定用户，当房间中没有用户便销毁房间  用户连接断开时调用 */
    void remove_room_user(const uint64_t uid)
    {
        // 1. 获取房间信息
        room_ptr rpm = get_room_by_uid(uid);
        if (rpm.get() == nullptr)
        {
            DBG_LOG("获取房间信息失败！");
            return;
        }

        // std::unique_lock<std::mutex> lock(_mutex);
        // 2. 从房间中移除用户
        rpm->handle_exit(uid);
        // 3. 检查房间中玩家数量，若为0，则销毁房间
        if (rpm->player_count() == 0)
        {
            remove_room(rpm->id());
        }
    }

private:
    user_table *_utb;                              // 用户信息管理的操作句柄
    online_user *_olu;                             // 在线用户管理模块的操作句柄
    uint64_t _next_rid;                            // 分配的room_id
    std::unordered_map<uint64_t, room_ptr> _rooms; // 房间id和房间信息
    std::unordered_map<uint64_t, uint64_t> _users; // 用户id和房间id
    std::mutex _mutex;
};

#endif
